package com.gitee.mocoffee.misc;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 这是一个15位的随机ID生成器 随机ID包含3部分信息:IP地址,时间戳,轮回数 其中时间戳是表示当前时间距离2018年1月1日的秒数;轮回数在0~32767之间轮回;
 * 故每个机器每秒钟最多有32768个不重复的ID
 * 上述3部分信息,IP地址用32bit表示,时间戳用28bit表示(至少用到2026年),轮回数用15bit表示
 * 全体信息共75bit,以32进制表示,共需要15个字符(每个字符表示5bit)
 *
 * @author lijia
 */
public class IdGenKit {
    private IdGenKit() {}

    private static final long ip;

    private static final AtomicInteger CYCLING_SEQ_SEED  = new AtomicInteger();
    private static final int           CYCLING_SEQ_RADIX = (int) Math.pow(2, 15);

    private static final long genesis = LocalDate.of(2018, Month.JANUARY, 1).atStartOfDay(ZoneId.systemDefault())
                                                 .toEpochSecond();

    /**
     * 32进制字符
     */
    private static final char[] CHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y'};

    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    static {
        String tempIP = Optional.ofNullable(SysinfoKit.me.loadLocalIPV4()).orElse("127.0.0.1");
        long longIP = 0;
        String[] ipStrs = tempIP.split("\\.");
        for (int i = 0; i < 4; i++) {
            longIP |= (Long.parseLong(ipStrs[i]) << (8 * (4 - i - 1)));
        }
        ip = longIP;
    }

    /**
     * @return 0~32767之间的循环数字 ,共占用15位
     */
    private static int genCyclingPart() {
        int seed = CYCLING_SEQ_SEED.incrementAndGet();
        return (seed & 0x7fffffff) % CYCLING_SEQ_RADIX;
    }

    /**
     * @return 当前时间到genesis(20170101)经过的秒数, 共占用28位
     */
    private static long genTimePart() {
        long now = System.currentTimeMillis() / 1000;
        return (now - genesis) & 0x0fffffff;
    }

    private static long[] genValue() {
        /*
        用2个long表示75bit信息,其中第一个long表示IP+时间戳=32bit+28bit=60bit;第二个long表示轮回数=15bit
         */
        long[] rst = new long[2];

        rst[0] = (ip << 28) | genTimePart();
        rst[1] = genCyclingPart();

        return rst;
    }

    private static String long2String(long number, int maxSize) {
        char[] chars = new char[maxSize];
        long index = 0;

        for (int i = 0; i < maxSize; i++) {
            index = (number >> (i * 5)) & 0x1f;
            chars[maxSize - 1 - i] = CHARS[(int) index];
        }

        return String.valueOf(chars);
    }

    private static long string2Long(String str) {
        long rst = 0;
        char[] chars = str.toCharArray();
        long value = 0;
        for (int i = 0; i < chars.length; i++) {
            value = Arrays.binarySearch(CHARS, chars[i]);
            rst |= (value << (5 * (chars.length - i - 1)));

        }
        return rst;
    }

    /**
     * 生成id
     *
     * @return 15位的随机id
     */
    public static String generate() {
        long[] value = genValue();
        return long2String(value[0], 12).concat(long2String(value[1], 3));
    }

    /**
     * 反解id
     *
     * @param id 15位的随机id
     * @return id中包含的信息
     */
    public static String explain(String id) {
        if (id.length() != 15) {
            throw new IllegalArgumentException("only support 20 digits id");
        }

        String part1 = id.substring(0, 12);
        String part2 = id.substring(12);

        long long1 = string2Long(part1);
        long cyclingPart = string2Long(part2);

        long ipPart = long1 >> 28;
        long timePart = long1 & 0x0fffffff;

        LocalDateTime time = LocalDateTime.ofEpochSecond(timePart + genesis, 0, ZoneOffset.ofHours(8));

        StringBuilder ip = new StringBuilder();
        for (int i = 0; i < 4; i++) {
            ip.append((ipPart >> (8 * (4 - 1 - i))) & 0xff).append('.');
        }
        ip.deleteCharAt(ip.length() - 1);

        StringBuilder rst = new StringBuilder();
        rst.append("ip=").append(ip);
        rst.append(",timestamp=").append(formatter.format(time));
        rst.append(",cycling number=").append(cyclingPart);

        return rst.toString();
    }
}
